package xyz.archknight.app.loginput.dictionary.gen.ui

import com.hankcs.hanlp.HanLP
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.swing.Swing
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.transactions.transaction
import xyz.archknight.app.loginput.dictionary.gen.bean.DatabaseProtocol
import xyz.archknight.app.loginput.dictionary.gen.bean.StatusProtocol
import xyz.archknight.app.loginput.dictionary.gen.dao.Cache
import xyz.archknight.app.loginput.dictionary.gen.dao.Caches
import xyz.archknight.app.loginput.dictionary.gen.dao.Word
import xyz.archknight.app.loginput.dictionary.gen.dao.Words
import xyz.archknight.app.loginput.dictionary.gen.service.EventBus
import xyz.archknight.app.loginput.dictionary.gen.ui.component.SVGButton
import xyz.archknight.app.loginput.dictionary.gen.ui.component.WordTable
import xyz.archknight.app.loginput.dictionary.gen.ui.model.WordTableModel
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.Insets
import javax.swing.*
import javax.swing.event.TableModelEvent

/**
 * 缓存区域管理页面
 *
 * @author midnite
 * create at 2020-06-17 8:55 上午
 */
class CacheStoragePane : JPanel() {
    private val constraints = GridBagConstraints()
    private val searchKeyword = JTextField()
    private val searchButton = SVGButton("magnifier", tip = "检索词库")
    private val resetSearchButton = SVGButton("back-circle", tip = "重置搜索")
    private val wordTableModel = WordTableModel()
    private val wordTable = WordTable(wordTableModel)
    private val scrollTable = JScrollPane(wordTable)
    private val addItemButton = SVGButton("plus", tip = "添加新条目")
    private val mergeItemButton = SVGButton("merge", tip = "合并全部所选内容")
    private val removeItemButton = SVGButton("delete", tip = "删除所选条目")
    private val reloadButton = SVGButton("refresh-circle", tip = "刷新当前页面")
    private val commitButton = SVGButton("correct", tip = "保存当前改动")
    private val cancelButton = SVGButton("error", tip = "撤销所有未保存改动")
    private val importButton = SVGButton("query-knowledge", tip = "从整段文字中提取词条")

    init {
        EventBus.database.subscribe(this::databaseEventRoute)
        EventBus.analysisQueue.subscribe(this::batchAddItems)
        EventBus.assemble.subscribe(this::assembleSelections)
        createUI()
    }

    private fun createUI() {
        layout = GridBagLayout()

        constraints.insets = Insets(4, 4, 4, 4)
        constraints.fill = GridBagConstraints.BOTH
        constraints.anchor = GridBagConstraints.NORTH
        addSubComponent(searchKeyword, weightX = 1.0, gridWidth = 10)
        searchKeyword.putClientProperty("JTextField.placeholderText", "输入关键词检索词库")
        addSubComponent(searchButton)
        addSubComponent(resetSearchButton, gridWidth = 0)

        addSubComponent(scrollTable, 1.0, 1.0, gridWidth = 0)
        wordTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
        wordTable.selectionModel.addListSelectionListener { event ->
            if (!event.valueIsAdjusting) {
                mergeItemButton.isEnabled = wordTable.selectedRows.size > 1
            }
        }
        createTableControl()

        wordTableModel.addTableModelListener(this::tableEventRoute)

        searchButton.addActionListener { loadData() }
        resetSearchButton.addActionListener {
            searchKeyword.text = ""
            EventBus.dbChange(DatabaseProtocol.RefreshCache)
        }
        mergeItemButton.addActionListener {
            val phases = wordTable.selectedRows.map { wordTableModel[it] }.map { it.phase }.toTypedArray()
            val dialog = MergeDialog(SwingUtilities.getRoot(this) as JFrame, phases)
            dialog.isVisible = true
        }
        checkDirty()
    }

    private fun createTableControl() {
        val separator = JPanel()
        addSubComponent(separator, 1.0, gridWidth = 5)
        addSubComponent(reloadButton)
        addSubComponent(addItemButton)
        addSubComponent(importButton)
        addSubComponent(mergeItemButton)
        addSubComponent(removeItemButton)
        addSubComponent(commitButton)
        addSubComponent(cancelButton)

        mergeItemButton.isEnabled = false

        reloadButton.addActionListener { EventBus.dbChange(DatabaseProtocol.RefreshCache) }
        commitButton.addActionListener { commitChanges() }
        cancelButton.addActionListener {
            if (wordTableModel.hasDirty()) {
                EventBus.dbChange(DatabaseProtocol.RefreshCache)
            }
        }

        addItemButton.addActionListener { wordTableModel.addNewRow(insertEvent = true) }
        removeItemButton.addActionListener { removeWord() }
        importButton.addActionListener {
            val importFrame = DocumentImportDialog(SwingUtilities.getRoot(this) as JFrame)
            importFrame.isVisible = true
        }
    }

    private fun addSubComponent(
        component: JComponent, weightX: Double = 0.0, weightY: Double = 0.0,
        gridWidth: Int = 1, gridHeight: Int = 1
    ) {
        constraints.weightx = weightX
        constraints.weighty = weightY
        constraints.gridwidth = gridWidth
        constraints.gridheight = gridHeight
        add(component, constraints)
    }

    private fun databaseEventRoute(action: DatabaseProtocol) {
        when (action) {
            DatabaseProtocol.RefreshCache,
            DatabaseProtocol.RefreshBoth,
            DatabaseProtocol.UpdateCache -> loadData()
            else -> {
            }
        }
    }

    private fun tableEventRoute(action: TableModelEvent) {
        when (action.type) {
            TableModelEvent.UPDATE -> {
                if (action.column == 0) {
                    val word = wordTableModel.getValueAt(action.firstRow, action.column) as String
                    val pinyin = HanLP.convertToPinyinString(word, "'", false)
                    wordTableModel.update(action.firstRow, word, pinyin)
                }
                if (action.column == 1){
                    wordTableModel.markDirty(action.firstRow)
                }
                checkDirty()
            }
            TableModelEvent.INSERT -> {
                wordTable.changeSelection(action.firstRow, 0, false, false)
                wordTable.editCellAt(action.firstRow, 0)
                wordTable.editorComponent?.requestFocus()
            }
            else -> {
            }
        }
    }

    private fun assembleSelections(assembledPhase: String) {
        val pinyin = HanLP.convertToPinyinString(assembledPhase, "'", false)
        val updateIndex = wordTable.selectedRows[0]
        val removeIndexes = wordTable.selectedRows.drop(1)
        GlobalScope.launch(Dispatchers.IO) {
            transaction {
                if (!wordTableModel.isNewLine(updateIndex)) {
                    Cache[wordTableModel[updateIndex].id].apply {
                        phase = assembledPhase
                        this.pinyin = pinyin
                    }
                    wordTableModel.update(updateIndex, assembledPhase, pinyin, false)
                } else {
                    wordTableModel.update(updateIndex, assembledPhase, pinyin)
                }
                removeIndexes.reversed().forEach {
                    if (!wordTableModel.isNewLine(it)) {
                        Cache[wordTableModel[it].id].delete()
                    }
                    wordTableModel.removeByRowOnly(it)
                }
            }
        }
    }

    private fun checkDirty() = GlobalScope.launch(Dispatchers.Swing) {
        commitButton.isEnabled = wordTableModel.hasDirty()
        cancelButton.isEnabled = wordTableModel.hasDirty()
    }

    private fun loadData() {
        if (wordTableModel.hasDirty()) {
            val confirm = JOptionPane.showConfirmDialog(
                this@CacheStoragePane,
                "当前页面已经有内容编辑过了，继续操作将导致这些内容丢失，是否确定？",
                "数据丢失提示",
                JOptionPane.YES_NO_OPTION,
                JOptionPane.QUESTION_MESSAGE
            )
            if (confirm == JOptionPane.NO_OPTION) {
                return
            }
        }
        GlobalScope.launch(Dispatchers.IO) {
            transaction {
                val wordsCount = Cache.count()
                EventBus.refreshStatus(StatusProtocol.Cache, wordsCount)
                val originalList = if (searchKeyword.text.isEmpty())
                    Cache.all().toList()
                else
                    Cache.find { Caches.phase like "%${searchKeyword.text}%" }.toList()
                wordTableModel.reset()
                originalList.forEach { wordTableModel.load(it.id.value, it.phase, it.pinyin) }
                checkDirty()
            }
        }
    }

    private fun commitChanges() {
        GlobalScope.launch(Dispatchers.IO) {
            val newPhases = wordTableModel.filter { it.dirty }.map { it.phase }
            val newPinyins = wordTableModel.filter { it.dirty }.map { it.pinyin }
            transaction {
                if (Cache.find {
                        (Caches.phase inList newPhases) and
                            (Caches.pinyin inList newPinyins) }.count() > 0) {
                    launch(Dispatchers.Swing) {
                        JOptionPane.showMessageDialog(
                            this@CacheStoragePane,
                            "待保存内容在数据库中已经存在，请检查。",
                            "词条已存在",
                            JOptionPane.ERROR_MESSAGE
                        )
                    }
                } else {
                    wordTableModel.filter { it.removed }
                        .forEach { rec ->
                            transaction {
                                Caches.deleteWhere { Caches.id eq rec.id }
                            }
                        }
                    wordTableModel.filter { it.dirty }
                        .forEach { rec ->
                            transaction {
                                if (rec.new) {
                                    Cache.new {
                                        phase = rec.phase
                                        pinyin = rec.pinyin
                                    }
                                } else {
                                    Cache.findById(rec.id)?.let { word ->
                                        if (word.phase != rec.phase) {
                                            word.phase = rec.phase
                                        }
                                        word.pinyin = rec.pinyin
                                    }
                                }
                            }
                        }
                    wordTableModel.markAllClean()
                }
            }
            EventBus.dbChange(DatabaseProtocol.UpdateCache)
        }
    }

    private fun removeWord() {
        if (wordTable.selectedRowCount > 0) {
            GlobalScope.launch(Dispatchers.Swing) {
                val newWords = wordTable.selectedRows
                    .filter { wordTableModel.isNewLine(it) }
                    .map { wordTableModel[it].phase }
                val oldWords = wordTable.selectedRows
                    .filter { !wordTableModel.isNewLine(it) }
                    .map { wordTableModel[it].id }
                launch(Dispatchers.IO) {
                    transaction {
                        println(oldWords)
                        oldWords.forEach { wordTableModel.removeById(it) }
                        newWords.forEach { wordTableModel.removeNewByPhase(it) }
                    }
                }
            }
        }
        checkDirty()
    }

    private fun batchAddItems(word: String) {
        if (!wordTableModel.exists(word)) {
            transaction {
                if (Word.find { Words.phase eq word }.count() == 0L) {
                    val pinyin = HanLP.convertToPinyinString(word, "'", false)
                    wordTableModel.addNewRow(word, pinyin)
                }
            }
        }
    }
}